import parole, pygame

import random, logging
import interface, dungeon, sim, town, highscore, command
import sim_creatures
from util import *
shader = parole.shader
from parole.colornames import colors

#############################################################################

version_str = '1.1.0-trunk'

#############################################################################

configFileName = None

titleScreenAction = None

schedule = None
mapFrame = None
cmdPoll = None
player = None
statBox = None
hpPercentBar = None
staminaPercentBar = None
statusBox = None
animateSim = 0

# annotations
transientAnnotes = []  # Annotations that are only displayed until the next
                       # input

waitTime = 500 # waiting is for half a second

levels = {}
currentLevel = None
commandToKeyMapping = None
originalAmbientIntensity = {} # map: intensity

music = {}
currentMusic = None
currentTrackName = None

freeForMonsterDesc = True

#############################################################################

# Largish bodies of text to be loaded from the resource package
introText = None
deathText = None
victoryText = None
helpText = None
levelDescs = None

#############################################################################

transientStacks = {} # tile -> text -> (count, annote)
messageHistory = []

def transient(text, posOrTile=None):
    t = None
    if posOrTile:
        if type(posOrTile) is tuple:
            t = mapFrame.getMap()[posOrTile]
        else:
            assert(type(posOrTile) is parole.map.Tile)
            t = posOrTile
    else:
        t = player.parentTile

    if t not in transientStacks:
        transientStacks[t] = {}

    if text not in transientStacks[t]:
        ann = mapFrame.annotate(t, text)
        transientStacks[t][text] = (1, ann)
        transientAnnotes.append(ann)
    else:
        count, ann = transientStacks[t][text]
        mapFrame.removeAnnotation(ann)
        transientAnnotes.remove(ann)
        count += 1
        ann = mapFrame.annotate(t, text + '{\Lime [x%d]}' % count)
        transientStacks[t][text] = (count, ann)
        transientAnnotes.append(ann)

    # add text to message history (most recent first)
    messageHistory.insert(0, text)
    parole.debug('Transient message at [%d,%d]: %r.', t.col, t.row, text)


#############################################################################

def loadText():
    global introText, deathText, victoryText, helpText, levelDescs
    introText = parole.resource.getResource('text/intro.txt') or 'Not Found!'
    deathText = parole.resource.getResource('text/death.txt') or 'Not Found!'
    victoryText = parole.resource.getResource('text/victory.txt') or 'Not Found!'
    helpText = parole.resource.getResource('text/help.txt') or 'Not Found!'

    introText = '{\Gold Introduction}\n' + introText
    deathText = '{\Gold Death Comes}\n' + deathText
    victoryText = '{\Gold Victory!}\n' + victoryText
    helpText = ('{\Gold Help!}\n' + helpText) % \
                                                    commandToKeyMapping

    levelDescs = {}
    levelDescs[1] = parole.resource.getResource('text/leveldesc/mines.txt')
    levelDescs[3] = parole.resource.getResource('text/leveldesc/grotto.txt')
    levelDescs[5] = parole.resource.getResource('text/leveldesc/catacombs.txt')
    levelDescs[6] = parole.resource.getResource('text/leveldesc/infernal.txt')

#############################################################################

levelTracks = {
    1: 'mines',
    2: 'mines',
    3: 'grotto',
    4: 'grotto',
    5: 'crypt',
    6: 'hell'
}

def loadMusic():
    music['titleScreen'] = \
        parole.resource.getSound(parole.conf.interface.music.titleScreen)
    music['town'] = \
        parole.resource.getSound(parole.conf.interface.music.town)
    music['death'] = \
        parole.resource.getSound(parole.conf.interface.music.death)
    music['victory'] = \
        parole.resource.getSound(parole.conf.interface.music.victory)
    music['levelup'] = \
        parole.resource.getSound(parole.conf.interface.music.levelup)
    music['mines'] = \
        parole.resource.getSound(parole.conf.interface.music.mines)
    music['grotto'] = \
        parole.resource.getSound(parole.conf.interface.music.grotto)
    music['crypt'] = \
        parole.resource.getSound(parole.conf.interface.music.crypt)
    music['hell'] = \
        parole.resource.getSound(parole.conf.interface.music.hell)

#############################################################################

def playMusic(trackname, loops=-1):
    global currentMusic, currentTrackName
    currentTrackName = trackname
    if not parole.conf.interface.music.enabled:
        return
    if trackname not in music:
        raise ValueError('Bad music track name')

    if currentMusic:
        currentMusic.stop()

    currentMusic = music[trackname]
    currentMusic.set_volume(parole.conf.interface.music.volume)
    currentMusic.play(loops)
    return currentMusic

def fadeMusicOut(time):
    global currentMusic, currentTrackName

    if not currentMusic:
        return

    currentMusic.fadeout(time)
    currentMusic = None
    currentTrackName = None

#############################################################################

def configChanged(conf):
    global currentTrackName
    if conf.interface.music.enabled:
        if (not currentMusic) and currentTrackName:
            playMusic(currentTrackName)
    elif currentMusic:
        track = currentTrackName
        fadeMusicOut(1000)
        currentTrackName = track


#############################################################################
# entry point

def doFrame():
    global levels, currentLevel
    # This is our main entry point, basically.

    # listen for changes to the configuration
    parole.conf.notify(configChanged)

    # Fonts that get used everywhere
    global proggySmall, arial, player, schedule
    proggySmall = parole.resource.getFont('fonts/ProggySmall.ttf', 16)
    arial = parole.resource.getFont('fonts/Arial.ttf', 12)

    interface.init()
    setupCommandToKeyMapping()
    loadText()
    loadMusic()

    global titleScreenAction
    while 1:
        player = None
        schedule = None
        parole.setDynamicLoggingExtras({})
        logging.getLogger().handlers[0].setFormatter(logging.Formatter(\
                '%(asctime)s [%(module)-10s] %(levelname)s: '\
                '%(message)s', datefmt='%H:%M:%S'))
        titleScreenAction = None
        # Start up with title screen
        parole.display.scene.removeAll()
        parole.display.clearSurface(parole.display.getSurface())
        #drawStarfield()
        showTitleScreen()
    
        # DEBUG: proceed directly to map
        #titleScreenAction = 'newGame'
        titleScreenAction = None

        # Start playing some sweet music
        if not currentMusic:
            playMusic('titleScreen', -1)
    
        # Wait till the user makes a choice
        while not titleScreenAction:
            yield
    
        # Handle the choice
        if titleScreenAction in ("quit", "newGame", "luckyPierre"):
            t = parole.time()
            fadeMusicOut(1000)
            for a in interface.fadeOut(1000):
                yield

        if titleScreenAction == "quit":
            raise parole.ParoleShutdown

        elif titleScreenAction == "help":
            weAreCurrently = {'waiting':True}
            def onClose():
                weAreCurrently['waiting'] = False
            interface.ModalBox(helpText, arial, onClose).push()
            while weAreCurrently['waiting']:
                yield

        elif titleScreenAction == "highScore":
            weAreCurrently = {'waiting':True}
            def onClose(x=None):
                weAreCurrently['waiting'] = False
            hs = highscore.getHighScore()
            hsText = "{\Gold High Score List:}\n\n%s" % hs
            interface.ModalBox(hsText, arial, onClose).push()
            while weAreCurrently['waiting']:
                yield

        elif titleScreenAction in ("newGame", "luckyPierre"):
            levels.clear()
            currentLevel = None
            weAreCurrently = {'waiting':False}

            if titleScreenAction == "newGame":
                # character creation
                weAreCurrently['waiting'] = True
                rlBox = parole.shader.ReadLineBox(arial, 300, 
                    prompt='Enter your name: ')
                frame = parole.shader.Frame(interface.frameBorders, 
                        contents=[parole.shader.ColorField((128,0,0,200),
                            (rlBox.width+10, rlBox.height)), rlBox])
                frame.update()
                def onEnterName(readline):
                    weAreCurrently['named'] = readline.text.strip()
                    if weAreCurrently['named']:
                        weAreCurrently['waiting'] = False
                        parole.display.scene.remove(frame)

                rl = parole.input.ReadLine(onEnterName, rlBox.onInput)
                parole.display.scene.add(frame, pos=frame.centeredPos())
                parole.pushUIEventHandler(rl)

                # start the town/intro music
                playMusic('town', 1)

                while weAreCurrently['waiting']:
                    yield

                #parole.display.update()
                parole.info('Player name: %s', weAreCurrently['named'])

                statUpper = interface.StatUpper(6, "Welcome, %s" %\
                    weAreCurrently['named'])
                statUpper.push()
                while statUpper.active:
                    yield
                strg, dex, con, per = statUpper.str, statUpper.dex,\
                                     statUpper.con, statUpper.per
            else:
                weAreCurrently['named'] = 'Lucky Pierre'
                strg, dex, con, per = 13, 13, 10, 10

            # A make a da playa
            schedule = sim.Schedule()
            player = sim_creatures.Player(weAreCurrently['named'],
                strg, dex, con, per)

            # Start formatting log messages with the simulation time at the
            # beginning
            parole.setDynamicLoggingExtras({'simtime':schedule.getTime})
            logging.getLogger().handlers[0].setFormatter(logging.Formatter(\
                    'SIMTIME %(simtime)s: [%(module)-10s] %(levelname)s: '\
                    '%(message)s'))

            # Let's do this thing!
            thetown, ustairPos, dstairPos = town.makeNewTown()#town.makeTown()
            addLevel('town', thetown, ustairPos, dstairPos)
            originalAmbientIntensity[thetown] = thetown.ambientIntensity

            for frame in runSim(schedule, thetown, ustairPos):
                if player and player.dead:
                    break
                elif player and player.winner:
                    break
                if statusBox:
                    statusBox.touch()
                yield

            # player died or won
            where = mapFrame.getMap().name

            # player died
            if player.dead:
                fadeMusicOut(500)
                clearTransientAnnotations()
                mapFrame.setMap(None)
                weAreCurrently = {'waiting':True}
                def onClose():
                    weAreCurrently['waiting'] = False
                txt = deathText
                if hasattr(player.lastDamageSource, 'killText'):
                    txt = player.lastDamageSource.killText
                interface.ModalBox(txt, arial, onClose).push()
                playMusic('death', 1)
                while weAreCurrently['waiting']:
                    yield
                for a in interface.fadeOut(1000):
                    yield

            # player won
            elif player.winner:
                fadeMusicOut(500)
                clearTransientAnnotations()
                mapFrame.setMap(None)
                weAreCurrently = {'waiting':True}
                def onClose():
                    weAreCurrently['waiting'] = False
                interface.ModalBox(victoryText, arial, onClose).push()
                playMusic('victory', 1)
                while weAreCurrently['waiting']:
                    yield
                for a in interface.fadeOut(1000):
                    yield

            # Create a high score entry for the dead/won player
            #if parole.conf.options.wizard:
            #    weAreCurrently = {'waiting':True}
            #    def onClose():
            #        weAreCurrently['waiting'] = False
            #    interface.ModalBox('No high score entry generated in wizard '
            #            'mode!', arial, onClose).push()
            #if player.xp <= 0:
            #    weAreCurrently = {'waiting':True}
            #    def onClose():
            #        weAreCurrently['waiting'] = False
            #    interface.ModalBox('No high score entry generated for a '
            #            'character with 0 XP!', arial, onClose).push()
            #else:

            hsEntry = highscore.Entry(player, where)
            if player.xp > 0:
                hs = highscore.getHighScore()
                hs.addEntry(hsEntry)
                hs.save()
                parole.info('New high score entry:\n%s', hsEntry)

            summaryText = str(hsEntry) + \
                    '\n\n{\Gold Last 10 Messages:}\n\n' + \
                    '\n'.join(reversed(messageHistory[:10]))

            weAreCurrently = {'waiting':True}
            def onClose():
                weAreCurrently['waiting'] = False
            interface.ModalBox('{\Gold New High Score Entry:}\n\n%s' % \
                    summaryText, arial, onClose).push()

            while weAreCurrently['waiting']:
                yield

            del messageHistory[:]

            weAreCurrently['waiting'] = True
            def __onChoose(c):
                weAreCurrently['waiting'] = False
                if str(c) == 'Yes':
                    weAreCurrently['identify'] = True
                elif str(c) == 'No':
                    weAreCurrently['identify'] = False

            interface.ObjectChooser([ 'Yes', 'No'],
                __onChoose, title="{\Gold Identify Inventory?}",
                choosableType=None, canCancel=False).push()

            while weAreCurrently['waiting']:
                yield

            if weAreCurrently['identify']:
                weAreCurrently['waiting'] = True
                def __describeItem(target):
                    desc = '{\Gold %s:}\n\n%s' % (target.noun(capitalized=1),
                            target.description())
                    interface.ModalBox(desc, arial, lambda: 0).push()
                def __cancel():
                    weAreCurrently['waiting'] = False

                for obj in player.inventory:
                    player.identify(obj, message=False)

                interface.ObjectChooser(interface.inventoryView(player.inventory),
                        __describeItem, cancelFn=__cancel, popOnChoice=False,
                    title="{\Gold You carried the following, for a total weight of %s lbs "
                    "(capacity %s lbs). Select an item for its detailed description.}" \
                            % (player.inventoryWeight(),
                        player.inventoryCapacity())).push()

                while weAreCurrently['waiting']:
                    yield

            fadeMusicOut(1000)
            for a in interface.fadeOut(1000):
                yield


#############################################################################

def showTitleScreen():
    # The main title bill (read from text/title.txt) centered high
    #parole.info(parole.display.screenTextSize(proggySmall))
    titleText = parole.resource.getResource('text/title.txt')
    tbTitle = parole.shader.TextBlockPass(proggySmall, (128,128,128), (0,0,0,0),
            text=titleText)
    tbTitle.update()
    titleRect = pygame.Rect((0,0), tbTitle.size)
    scrRect = parole.display.getSurface().get_rect()
    titleRect.centerx = scrRect.centerx
    titleRect.top = 200
    parole.display.scene.add(tbTitle, pos=titleRect.topleft)

    # Subtitle five pixels under the main
    tbSubTitle = parole.shader.TextBlockPass(arial, (255,255,255), 
            text="Halls of Darkness")
    tbSubTitle.update()
    subRect = pygame.Rect((0,0), tbSubTitle.size)
    subRect.center = titleRect.center
    subRect.top = titleRect.bottom + 5
    parole.display.scene.add(tbSubTitle, pos=subRect.topleft)

    # Horziontal lines flanking the subtitle
    lineL = parole.shader.Line((titleRect.left, subRect.centery),
                               (subRect.left, subRect.centery),
                               parole.colornames.colors['Red'])
    parole.display.scene.add(lineL, pos=lineL.defaultPos)
    lineR = parole.shader.Line((subRect.right+1, subRect.centery),
                               (titleRect.right, subRect.centery),
                               parole.colornames.colors['Red'])
    parole.display.scene.add(lineR, pos=lineR.defaultPos)

    # LineChooser in a frame
    hs = highscore.getHighScore()
    choices = [
        ('New Game', newGame),
        ('Options', doOptions),
        len(hs) and ('High Scores', doHighScore) or ('{\Gray High Scores}',
            lambda x: None),
        ('Help', help),
        ('Quit', quit)
    ]
    if parole.conf.options.wizard:
        choices.insert(0, ('Lucky Pierre', luckyPierre))
    chFrame = interface.LineChooserFrame(proggySmall, 
            choices)
    chFrame.update()
    frameRect = pygame.Rect((0,0), chFrame.size)
    frameRect.centerx = titleRect.centerx
    frameRect.top = subRect.bottom + 20
    parole.display.scene.add(chFrame, pos=frameRect.topleft)

    parole.pushUIEventHandler(chFrame.chooser.cmdMap)

    # Copyright info at bottom
    tbCopy = shader.TextBlockPass(arial, (180,180,180), (0,0,0),
        text="Version %s. Copyright 2012 by Max Bane, "
             "John Greenberg, and Greg Greenberg. Distributed under the "
             "GPL v2." % (version_str,))
    tbCopy.update()
    copyRect = pygame.Rect((0,0), tbCopy.size)
    copyRect.bottom = scrRect.bottom-2
    copyRect.centerx = scrRect.centerx
    parole.display.scene.add(tbCopy, pos=copyRect.topleft)

#############################################################################

def quit(line):
    global titleScreenAction
    titleScreenAction = "quit"

#############################################################################

def newGame(line):
    global titleScreenAction
    titleScreenAction = "newGame"

def luckyPierre(line):
    global titleScreenAction
    titleScreenAction = "luckyPierre"

#############################################################################

def doHighScore(line):
    global titleScreenAction
    titleScreenAction = "highScore"

#############################################################################

def doOptions(line):
    interface.OptionsMenu().push()

#############################################################################

def help(line):
    global titleScreenAction
    titleScreenAction = "help"

#############################################################################

def setupCommandToKeyMapping():
    global commandToKeyMapping
    commandToKeyMapping = {}
    for key,cmd in parole.conf.interface.mainplay.keypresses.iteritems():
        if cmd not in commandToKeyMapping:
            commandToKeyMapping[cmd] = []
        commandToKeyMapping[cmd].append(key)

    for cmd in commandToKeyMapping:
        commandToKeyMapping[cmd] = r'{\Gold /}'.join(commandToKeyMapping[cmd])

#############################################################################

def setCurrentMap(map, startPos):
    global currentLevel

    if player.parentTile:
        #player.listen(sim.Event('leave tile', schedule.time, (player,
        #    player.pos), dests=[player]))
        schedule.sendNow(sim.Event('leave tile', schedule.time, (player,
            player.pos), dests=[player]))

    clearTransientAnnotations()
    if mapFrame.getMap():
        mapFrame.setMap(None)

    mapFrame.setMap(map)
    mapFrame.centerAtTile(startPos)

    map[startPos].add(player)
    #player.listen(sim.Event('enter tile', schedule.time, (player, startPos, map),
    #    dests=[player]))
    schedule.sendNow(sim.Event('enter tile', schedule.time, (player, startPos,
        map), dests=[player]))
    
    for level, info in levels.iteritems():
        if info[0] is map:
            currentLevel = level

    # map music
    trackName = None
    if currentLevel == 'town':
        trackName = 'town'
    elif currentLevel in levelTracks:
        #trackName = 'level_%d' % currentLevel
        trackName = levelTracks[currentLevel]
    if trackName and currentTrackName != trackName:
        fadeMusicOut(500)
        playMusic(trackName, -1)

    map.update()
    mapFrame.update()

#############################################################################

def addLevel(level, map, ustairPos, dstairPos):
    levels[level] = (map, ustairPos, dstairPos)

#############################################################################

def pushSimAnimation():
    global animateSim
    animateSim += 1

def popSimAnimation():
    global animateSim
    animateSim -= 1
    if animateSim < 0:
        animateSim = 0

#############################################################################

def runSim(sched, curMap, startPos):
    global player, schedule, mapFrame, statBox, statusBox, cmdPoll,\
           currentLevel, freeForMonsterDesc, hpPercentBar, staminaPercentBar
    schedule = sched

    if not player:
        parole.warn('runSim: null player, creating default one.')
        player = sim_creatures.Player()

    if not curMap:
        #curMap, ustairPos, dstairPos = town.makeTown()
        curMap, ustairPos, dstairPos = dungeon.makeLevel(1)
        addLevel(1, curMap, ustairPos, dstairPos)
        startPos = ustairPos
        originalAmbientIntensity[curMap] = curMap.ambientIntensity

    # set up the status display
    statBox = interface.StatBox(proggySmall, player)
    statBox.update()

    # set up the hp percent bar
    hpPercentBar = interface.StatPercentBar(player, 'hp', 'maxHP', 
            (statBox.centeredPos()[0] - 8, statBox.height / 2 - 8),
            (0, 128, 0), (255, 0, 0), (0, 0, 255), label="Hit Points",
            labelColor=(0,255,0))
    hpPercentBar.update()

    # set up the stamina percent bar
    staminaPercentBar = interface.StatPercentBar(player, 'stamina', 'maxStamina', 
            (statBox.centeredPos()[0] - 8, statBox.height / 2 - 8),
            (128, 128, 0), label="Stamina", labelColor=(255,255,0))
    staminaPercentBar.update()

    # put the percent bars in a frame together
    percentFrame = shader.Frame((None, None, None, 
            shader.HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
            None, None, None, None))
    percentFrame.addPass(shader.ColorField((164,0,0,128),
        (statBox.centeredPos()[0], statBox.height-5)))
    percentFrame.addPass(hpPercentBar, pos=(4,4))
    percentFrame.addPass(staminaPercentBar, pos=(4,4+hpPercentBar.height+4))
    percentFrame.update()

    # set up status box
    statusBox = interface.StatusBox(proggySmall, player,
            (statBox.centeredPos()[0], statBox.height-5))
    statusBox.update()

    # Set up a map frame 
    scrSize = parole.display.getSurface().get_size()
    mapFrame = parole.map.MapFrame((scrSize[0], scrSize[1]-statBox.height))

    # display them
    parole.display.scene.add(mapFrame, pos=(0, statBox.height))
    parole.display.scene.add(statBox, pos=(statBox.centeredPos()[0], 0))
    parole.display.scene.add(percentFrame, pos=(0,0))
    parole.display.scene.add(statusBox,
            pos=(statBox.centeredPos()[0]+statBox.width, 0))

    # be sure to unlisten before saving!
    schedule.listen('set stat', statBox) 
    schedule.listen('increase stat', statBox) 
    schedule.listen('decrease stat', statBox) 
    schedule.listen('enter inventory', statBox)
    schedule.listen('leave inventory', statBox)
    schedule.listen('set stat', hpPercentBar) 
    schedule.listen('increase stat', hpPercentBar) 
    schedule.listen('decrease stat', hpPercentBar) 
    schedule.listen('get command', hpPercentBar) 
    schedule.listen('set stat', staminaPercentBar) 
    schedule.listen('increase stat', staminaPercentBar) 
    schedule.listen('decrease stat', staminaPercentBar) 

    # Set up a ui event handler for player commands
    #cmdPoll = command.CommandPoll()
    #schedule.listen('get command', cmdPoll)
    #cmap = parole.input.CommandMap(parole.conf.interface.mainplay, 
    #        handleCommandOld)
    # new command handling:
    command.init(schedule, player)
    cmap = parole.input.CommandMap(parole.conf.interface.mainplay, 
            command.handleCommand)
    parole.pushUIEventHandler(cmap)

    # start listening for commands at the next game-millisecond
    schedule.add(sim.Event('get command', 1))
    
    # set the map and add the player to the map
    setCurrentMap(curMap, startPos)

    # show intro
    weAreCurrently = {'waiting':True}
    def onClose():
        weAreCurrently['waiting'] = False
    if not parole.conf.options.wizard:
        interface.ModalBox(introText, arial, onClose).push()

    while 1:
        #if transientAnnotes and mapFrame.selectedTile:
        #    mapFrame.selectTile(None)
        parole.clearAnimations()

        # check for leveling
        if (player.level + 1) in player.levelXP:
            if player['xp'] >= player.levelXP[player.level+1]:
                curTrack = currentTrackName
                playMusic('levelup', -1)
                statUpper = interface.StatUpper(4)
                statUpper.push()
                while statUpper.active:
                    yield
                fadeMusicOut(500)
                playMusic(curTrack, -1)

                player.level += 1
                schedule.sendNow(sim.Event('set stat', schedule.time, (player,
                    'strength', statUpper.str), dests=[player]))
                schedule.sendNow(sim.Event('set stat', schedule.time, (player,
                    'dexterity', statUpper.dex), dests=[player]))
                schedule.sendNow(sim.Event('set stat', schedule.time, (player,
                    'constitution', statUpper.con), dests=[player]))
                schedule.sendNow(sim.Event('set stat', schedule.time, (player,
                    'perception', statUpper.per), dests=[player]))
                schedule.sendNow(sim.Event('increase stat', schedule.time, (player,
                    'hp', 0.33*player['maxHP']), dests=[player]))

        #while not cmdPoll.awaitCommand:
        while not command.cmdPoll.awaitCommand:
            parole.debug('ADVANCING SCHEDULE...')
            eventsSent = schedule.advance()
            if parole.conf.interface.animateAllMovement or animateSim:
                for event in eventsSent:
                    if event.id == 'translocate' and event.args[0] is not player:
                        parole.debug('******** ANIMATING SIM **********')
                        if player.pos:
                            mapFrame.update()
                            player.updateHighlights()
                        parole.display.update()
                        break

        if player.pos:
            # update the mapframe if the player is currently located anywhere
            mapFrame.update()
            player.updateHighlights()
        #parole.display.update()

        # new level description
        if currentLevel in levelDescs and currentLevel not in player.levelsSeen:
            if (not player.dead) and player.tileIsBrightEnough(player.parentTile):
                freeForMonsterDesc = False
                player.levelsSeen.add(currentLevel)
                def onClose():
                    global freeForMonsterDesc
                    freeForMonsterDesc = True
                interface.ModalBox(levelDescs[currentLevel], arial, 
                        onClose).push()
        elif freeForMonsterDesc:
            # pop up monster description
            checkFirstEncounter()

        yield

def checkFirstEncounter():
    if parole.conf.options.wizard:
        return False

    # pop up description of any new monster in fov
    for visPos in mapFrame.visibleTiles:
        t = mapFrame.getMap()[visPos]
        if not player.tileIsBrightEnough(t):
            continue
        for obj in t:
            if isinstance(obj, sim_creatures.Monster) and obj.description:
                monName = obj.name
                if monName not in player.encounteredMonsters:
                    player.encounteredMonsters.add(monName)
                    interface.ModalBox(
                            ('{\Gold First encounter with %s:}\n' %\
                                    obj.noun()) + obj.description,
                            arial,
                            lambda: None).push()
                    foundMon = True
                    return True
    return False

def clearTransientAnnotations():
    # Clear transient annotations
    for ann in transientAnnotes:
        if ann:
            mapFrame.removeAnnotation(ann)
    del transientAnnotes[:]
    transientStacks.clear()
    if player and player.parentTile:
        mapFrame.selectTile(player.pos)

class MapSender(sim.Listener):
    def __init__(self, nm, sp):
        self.nm, self.sp = nm, sp
    def listen(self, event):
        if event.id == 'new map':
            msg = event.args
            m = interface.messageBox('Patience...')
            parole.display.update()
            setCurrentMap(self.nm, self.sp)
            schedule.unlisten('new map', self)
            schedule.add(sim.Event('get command', schedule.time+1))
            parole.display.scene.remove(m)
            if msg:
                transient(msg)

#========================================

class FallDamage(object):
    def __init__(self):
        self.killText = \
"""
{\Gold *THWOMP*}

You land at an awkward angle, shattering several bones in your neck. You linger
on the ground briefly, dazed and motionless, your vision a blur, your ears
ringing. You die quickly. It is not painless.
""".strip()

    def __str__(self):
        return 'a nasty fall'

class Suicide(object):
    def __init__(self):
        self.killText = \
"""
{\Gold Suicide}

The darkness. The suffocating darkness. It's all around you. The walls feel like
they're closing in. Shuffling sounds in the distance. Blood trickles from a cut
in your forehead. There is nothing but horror and darkness. There is no way to
end it... but you can end yourself. You unsheath your knife and slit your
throat.
""".strip()

    def __str__(self):
        return 'a suicidal despair'

#========================================

